
/*
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: GPL-3.0+
 * License-Filename: LICENSE
 *
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>

#include "splay-tree.h"
#include "main.h"
#include "parse.h"
#include "parsedot.h"
#include "uniqstring.h"
#include "color.h"
#include "options.h"
#include "nes.h"
#include "uniqnode.h"
#include "mem.h"

/* pending edge data last seen */
struct usubg *pe_lastsubg = NULL;
struct unode *pe_lastnode = NULL;
int pe_lasttoken = 0;

/* free function for splay edge cache value data */
static void is_a_dotne_free_value(splay_tree_value ptr)
{
	if (ptr) {
		myfree((void *)ptr, __FUNCTION__, __LINE__);
	}
	return;
}

/* has token a compass point return it' s string or (char *) 0 if not */
static char *is_a_dotne_iscompass(void);

/* node or edge */
int is_a_dotne(struct usubg *subg)
{
	char *fname = NULL;
	char *tname = NULL;
	char *elabel = NULL;
	int elabel_set = 0;
	int color = 0;
	int color_set = 0;
	int fcolor = 0;
	int fcolor_set = 0;
	char *ffontname = NULL;
	int ffontname_set = 0;
	char *efontsize = NULL;
	int efontsize_set = 0;
	char *stylestr = NULL;
	unsigned int style = ESTYLE_NORMAL;
	int style_set = 0;
	char *stylep = NULL;
	int thickness = 0;	/* default thickness of edge line */
	int thickness_set = 0;
	char *penwstr = NULL;
	double thicknessd = 0.0;
	struct unode *fnode = NULL;
	struct unode *tnode = NULL;
	struct uedge *ue = NULL;
	splay_tree ect = NULL;	/* edge cache */
	splay_tree_node spn = NULL;
	int nect = 0;		/* number of entries in splay */
	struct ecdot *ecptr = NULL;
	int i = 0;
	char *nport = NULL;
	char *ncompass = NULL;
	int ret = 0;
	int fbrace = 0;
	int tbrace = 0;
	struct usubg *fsubg = NULL;
	struct usubg *tsubg = NULL;
	int cstatus = 0;
	int cscode = 0;
	int cscode_set = 0;
	char *csstr = NULL;

	/* start is a node name ID:port:compass_point or ID:compass_point
	 * or "id" or 123 or q;
	 */
	if (token == DOT_STRING) {
		/* */
	} else if (token == DOT_ID) {
		/* */
	} else if (token == DOT_NUM) {
		/* */
	} else if (token == DOT_CHAR) {
		/* */
	} else {
		/* not a start of a node or edge */
		return (0);
	}
	/* */
	elabel_set = 0;
	elabel = NULL;
	nport = NULL;
	ncompass = NULL;
	/* white node background color */
	color = 0x00ffffff;
	/*
	 * id for node in a node or edge is defined as:
	 * node_id: id port
	 * port:
	 *  ":" id (":" compass_pt)
	 *  ":" compass_pt
	 * compass_pt: "n" "ne" "e" "se" "s" "sw" "w" "nw" "c" "_"
	 */
	if (token == DOT_STRING) {
		fname = laststring;
	} else if (token == DOT_NUM) {
		fname = lastnum;
	} else if (token == DOT_ID) {
		fname = lastid;
	} else if (token == DOT_CHAR) {
		fname = lastchar;
	} else {
		fname = NULL;
	}
	if (option_parsedebug) {
		printf("%s(): expecting node/edge statement fname is `%s'\n", __FUNCTION__, fname);
		fflush(stdout);
	}
	/* */
	if (fname == NULL) {
		fname = NULL;
		parser_error_string = "nil from name in node or edge";
		return (0);
	}
	/* check for empty "" string */
	if (strlen(fname) == 0) {
		fname = NULL;
		parser_error_string = "empty from name in node or edge";
		return (0);
	}
	/* : or --/-> or [ or id or '=' at bogus=foo; */
	token = dot_lex();
	if (token == DOT_IS) {
		if (option_parsedebug) {
			printf("%s(): expected node/edge but this is a bogus=nonsense; thingie fname is `%s'\n", __FUNCTION__,
			       fname);
			fflush(stdout);
		}
		dot_unlex();
		return (0);
	}
	/* */
	if (token == DOT_COLON) {
		/* id:port or id:compass_point */
		token = dot_lex();
		ncompass = is_a_dotne_iscompass();
		if (ncompass == NULL) {
			/* this is a port and not a compass */
		} else {
			/* this is a compass point */
		}
		/* --/-> or [ or id or ': ' */
		token = dot_lex();
		if (token == DOT_COLON) {
			/* lastid has compass_point in id:port:compass_point */
			token = dot_lex();
			ncompass = is_a_dotne_iscompass();
			if (ncompass == NULL) {
				/* is not a compass point and should have been */
			} else {
				/* this is an expected compass point */
			}
			/* --/-> or [ or id */
			token = dot_lex();
		}
	}

	/* check if there is a pending edge from a subgraph to this node */
	if (pe_lasttoken) {
		/* */
		if (option_parsedebug) {
			printf("%s(): fname=%s pe_lasttoken found lastnode=%p lastsubg=%p\n", __FUNCTION__, fname,
			       (void *)pe_lastnode, (void *)pe_lastsubg);
			fflush(stdout);
		}

		/* edge from subgraph to node */
		if (pe_lastsubg) {
			dot_desadd(NULL, pe_lastsubg, fname, NULL, DES_S_N);
		}

		/* clear because data is handled */
		dot_pe_clear();
	}

	/* run for node if no edge code */
	if ((token != DOT_UEDGE) && (token != DOT_DEDGE)) {
		if (option_parsedebug) {
			printf("%s(): expecting node fname `%s' port `%s' compass `%s'\n", __FUNCTION__, fname, nport, ncompass);
			fflush(stdout);
		}
		ret = is_a_dotne_node(subg, fname, nport, ncompass);
		return (ret);
	}

	/* token is a edge -- or -> */
	if (option_parsedebug) {
		printf("%s(): expecting edge starting at `%s'\n", __FUNCTION__, fname);
		fflush(stdout);
	}

	/* id "string" or brace open for subgraph */
	token = dot_lex();
	fbrace = 0;
	tbrace = 0;
	fsubg = NULL;
	tsubg = NULL;
	if (token == DOT_STRING) {
		tname = laststring;
	} else if (token == DOT_NUM) {
		tname = lastnum;
	} else if (token == DOT_ID) {
		tname = lastid;
	} else if (token == DOT_CHAR) {
		tname = lastchar;
	} else if (token == DOT_BO) {
		tname = NULL;
		/* to is a compound */
		tbrace = 1;
	} else {
		tname = NULL;
	}

	/* to is a name or a {} */
	if (tbrace) {
		/* parse a {...} */
		cstatus = is_a_dotcompoundgraph(subg);
		if (cstatus == 0) {
			/* parser error is set in compoundgraph */
			return (0);
		}

		/* */
		if (option_parsedebug) {
			printf("%s(): fname=%s pe_lasttoken found lastnode=%p lastsubg=%p\n", __FUNCTION__, fname,
			       (void *)pe_lastnode, (void *)pe_lastsubg);
			fflush(stdout);
		}

		/* from node exists? */
		fnode = uniqnode(fname);

		/* if 0 did not exist, create now */
		if (fnode == (struct unode *)0) {
			fnode = unode_new(fname);
			/* set defaults from "node[...]" */
			set_node_defaults(fnode, subg);
			fnode->name = fname;
			/* initial label is node name */
			if (fnode->labeltype) {
				/* use preset */
				if (fnode->label == NULL) {
					fnode->label = fname;
				}
			} else {
				fnode->label = fname;
			}
			fnode->rootedon = subg;
			/* node is defined by edge statement */
			fnode->bitflags.defbyedge = 1;
			fnode->bitflags.defbynode = 0;
			uniqnode_add(fnode->name, fnode);
			parsednl_add(fnode);
		} else {
			/* already defined node */
		}

		/* save to-subgraph */
		tsubg = pe_lastsubg;
		/* edge from subgraph to node */
		if (pe_lastsubg) {
			dot_desadd(fname, NULL, NULL, pe_lastsubg, DES_N_S);
		}

		/* clear because data is handled */
		dot_pe_clear();
	} else {
		/* */
		if (tname == NULL) {
			tname = NULL;
			parser_error_string = "expected to name in edge";
			return (0);
		}
		/* check for empty "" string */
		if (strlen(tname) == 0) {
			tname = NULL;
			parser_error_string = "empty to name in edge";
			return (0);
		}
		/* : or [ or id */
		token = dot_lex();
		if (token == DOT_COLON) {
			/* id:compass_point or id:port */
			token = dot_lex();
			ncompass = is_a_dotne_iscompass();
			if (ncompass == NULL) {
				/* this is a port */
			} else {
				/* this is a compass point */
			}
			/* [ or id of next edge-or-node or or : or ; */
			token = dot_lex();
			if (token == DOT_COLON) {
				/* lastid has compass_point in id:port:compass_point */
				token = dot_lex();
				ncompass = is_a_dotne_iscompass();
				if (ncompass == NULL) {
					/* should have been a compass point */
				} else {
					/* expected compass point */
				}
				/* --/-> or [ or id */
				token = dot_lex();
			}
		}
		/* create the edge cache */
		ect = splay_tree_new(splay_tree_compare_ints, NULL, is_a_dotne_free_value);
		/* number of entries in splay */
		nect = 0;
		/* create value for first edge */
		ecptr = mymalloc(sizeof(struct ecdot), __FUNCTION__, __LINE__);
		ecptr->fromname = fname;
		ecptr->toname = tname;
		/* add in splay tree */
		splay_tree_insert(ect, (splay_tree_key) nect, (splay_tree_value) ecptr);
		/* number of entries in splay */
		nect = nect + 1;
	}

	/* after first -- or -> more can follow here */
	if (token == DOT_UEDGE || token == DOT_DEDGE) {
		/* expect more edges */
		for (;;) {
			/* to becomes from */
			fbrace = tbrace;
			fsubg = tsubg;
			fname = tname;
			/* id "string" */
			token = dot_lex();
			if (token == DOT_STRING) {
				tname = laststring;
			} else if (token == DOT_NUM) {
				tname = lastnum;
			} else if (token == DOT_ID) {
				tname = lastid;
			} else if (token == DOT_CHAR) {
				tname = lastchar;
			} else if (token == DOT_BO) {
				/* expect subgraph */
				tbrace = 1;
				tname = NULL;
			} else {
				tname = NULL;
			}

			/* to can be name or subgraph */
			if (tbrace) {
				/* to is a subgraph */

				/* parse a {...} */
				cstatus = is_a_dotcompoundgraph(subg);
				if (cstatus == 0) {
					/* parser error is set in compoundgraph */
					if (ect) {
						ect = splay_tree_delete(ect);
					}

					return (0);
				}

				/* set token past the close brace of compound */
				token = dot_lex();
				/* if from is a name check exists */
				if (fname) {
					fnode = uniqnode(fname);
					if (fnode == (struct unode *)0) {
						fnode = unode_new(fname);
						/* set defaults from "node[...]" */
						set_node_defaults(fnode, subg);
						fnode->name = fname;
						/* initial label is node name */
						if (fnode->labeltype) {
							/* use preset */
							if (fnode->label == NULL) {
								fnode->label = fname;
							}
						} else {
							fnode->label = fname;
						}

						fnode->rootedon = subg;
						/* node is defined by edge statement */
						fnode->bitflags.defbyedge = 1;
						fnode->bitflags.defbynode = 0;
						uniqnode_add(fnode->name, fnode);
						parsednl_add(fnode);
					} else {
						/* already defined node */
					}
				}

				/* save to-subgraph */
				tsubg = pe_lastsubg;
				/* from can be a name or subgraph now */
				if (fname) {
					/* edge from node to subgraph */
					if (pe_lastsubg) {
						dot_desadd(fname, NULL, NULL, pe_lastsubg, DES_N_S);
					}
				}

				if (fsubg) {
					/* edge from subgraph to subgraph */
					if (pe_lastsubg) {
						dot_desadd(NULL, fsubg, NULL, pe_lastsubg, DES_S_S);
					}
				}

				/* clear because data is handled */
				dot_pe_clear();
			} else {
				/* to is a name, not subgraph */
				tbrace = 0;
				tsubg = NULL;
				/* to is a name */
				if (tname == NULL) {
					tname = NULL;
					parser_error_string = "expected to name in edge";
					ect = splay_tree_delete(ect);
					return (0);
				}
				/* check for "" empty string */
				if (strlen(tname) == 0) {
					tname = NULL;
					parser_error_string = "empty to name in edge";
					ect = splay_tree_delete(ect);
					return (0);
				}
				/* : or [ or id */
				token = dot_lex();
				if (token == DOT_COLON) {
					/* id:port or id:compass_point */
					token = dot_lex();
					ncompass = is_a_dotne_iscompass();
					if (ncompass == NULL) {
						/* this is a port */
					} else {
						/* this is a compass point */
					}
					/* [ or id of next edge-or-node or : or ; */
					token = dot_lex();
					if (token == DOT_COLON) {
						/* lastid has compass_point in id:port:compass_point */
						token = dot_lex();
						ncompass = is_a_dotne_iscompass();
						if (ncompass == NULL) {
							/* this should have been a compass point */
						} else {
							/* is expected compass point */
						}
						/* --/-> or [ or id */
						token = dot_lex();
					}
				}
				/* from can be subgraph or name */
				if (fname) {
					/* create value for next edge */
					ecptr = mymalloc(sizeof(struct ecdot), __FUNCTION__, __LINE__);
					ecptr->fromname = fname;
					ecptr->toname = tname;
					/* add in splay tree */
					splay_tree_insert(ect, (splay_tree_key) nect, (splay_tree_value) ecptr);
					/* number of entries in splay */
					nect = nect + 1;
				} else {
					/* from is a subgraph and to a name */
					tnode = uniqnode(tname);
					if (tnode == (struct unode *)0) {
						tnode = unode_new(tname);
						/* set defaults from "node[...]" */
						set_node_defaults(tnode, subg);
						tnode->name = tname;
						/* initial label is node name */
						if (tnode->labeltype) {
							/* use preset */
							if (tnode->label == NULL) {
								tnode->label = tname;
							}
						} else {
							tnode->label = tname;
						}

						tnode->rootedon = subg;
						/* node is defined by edge statement */
						tnode->bitflags.defbyedge = 1;
						tnode->bitflags.defbynode = 0;
						uniqnode_add(tnode->name, tnode);
						parsednl_add(tnode);
					} else {
						/* already defined node */
					}

					if (fsubg) {
						/* edge from subgraph to name */
						dot_desadd(NULL, fsubg, tname, NULL, DES_S_N);
					}

					if (fbrace) {
					}

				}
			}

			/* check for -- or -> follow */
			if (token == DOT_UEDGE || token == DOT_DEDGE) {
				/* go at start of loop */
				continue;
			}

			/* check for start of options with a [ */
			if (token == DOT_BRACKETO) {
				break;
			} else {
				/* something else or a ; */
				break;
			}
		}
	}
	/* now start the options parsing */
	fcolor = 0;
	fcolor_set = 0;
	ffontname = NULL;
	ffontname_set = 0;
	color = 0;
	color_set = 0;
	style = ESTYLE_NORMAL;
	stylep = NULL;
	thickness = 0;
	thickness_set = 0;
	penwstr = NULL;
	thicknessd = 0.0;
	elabel = NULL;
	elabel_set = 0;
	stylestr = NULL;
	style = ESTYLE_NORMAL;
	style_set = 0;
	efontsize = NULL;
	efontsize_set = 0;
	cscode = 0;
	cscode_set = 0;
	csstr = NULL;

	/* optional attributes of edge with a [ */
	if (token == DOT_BRACKETO) {
		for (;;) {
			token = dot_lex();
			/* check for eof */
			if (token == 0) {
				parser_error_string = "un-expected end-of-file at edge options";
				ect = splay_tree_delete(ect);
				return (1);
			}

			if (token == DOT_COMMA) {
				/* skip optional comma */
				token = dot_lex();
			}

			if (token == DOT_SEMIC) {
				/* skip optional semicomma */
				token = dot_lex();
			}

			if (token == DOT_BRACKETC) {
				/* end of options */
				break;
			}

			/* 3) keep on reading the edge options
			 * URL arrowhead arrowsize arrowtail color colorscheme comment constraint
			 * decorate dir edgeURL edgehref edgetarget edgetooltip fontcolor fontname
			 * fontsize headURL head_lp headclip headhref headlabel headport headtarget
			 * headtooltip href id label labelURL labelangle labeldistance labelfloat
			 * labelfontcolor labelfontname labelfontsize labelhref labeltarget
			 * labeltooltip layer len lhead lp ltail minlen nojustify penwidth
			 * pos samehead sametail showboxes style tailURL tail_lp tailclip tailhref
			 * taillabel tailport tailtarget tailtooltip target tooltip weight xlabel xlp
			 */

			if (token == DOT_ID) {
				if (strcmp(lastid, "URL") == 0) {
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge URL";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						/* */
					} else if (token == DOT_ID) {
						/* */
					} else {
						parser_error_string = "expected string at edge URL";
						ect = splay_tree_delete(ect);
						return (1);
					}
				} else if (strcmp(lastid, "arrowhead") == 0) {
					/* arrow head type */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge arrowhead";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						/* */
					} else if (token == DOT_ID) {
						/* */
					} else {
						parser_error_string = "expected string at edge arrowhead";
						ect = splay_tree_delete(ect);
						return (1);
					}
					/* basic arrowhead names are box,crow,curve,icurve
					 * diamond,dot,inv,none,normal,tee,vee
					 * with lro modifiers into many possible names to use
					 * to parse seperately
					 */
				} else if (strcmp(lastid, "arrowsize") == 0) {
					/* arrow scale factor, double, default 1.0 */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge arrowsize";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						/* */
					} else if (token == DOT_NUM) {
						/* */
					} else {
						parser_error_string = "expected double number at edge arrowsize";
						ect = splay_tree_delete(ect);
						return (1);
					}
				} else if (strcmp(lastid, "arrowtail") == 0) {
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge arrowtail";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						/* */
					} else if (token == DOT_ID) {
						/* */
					} else {
						parser_error_string = "expected string at edge arrowtail";
						ect = splay_tree_delete(ect);
						return (1);
					}
					/* basic arrowhead names are box,crow,curve,icurve
					 * diamond,dot,inv,none,normal,tee,vee
					 * with lro modifiers into many possible names to use
					 * to parse seperately
					 */
				} else if (strcmp(lastid, "color") == 0) {
					/* edge color */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge color";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						color = colorcode(laststring);
						color_set = 1;
						/* check for unknown color */
						if (color == -1) {
							/* black edge line */
							color = 0;
							color_set = 0;
						}
					} else if (token == DOT_ID) {
						color = colorcode(lastid);
						color_set = 1;
						/* check for unknown color */
						if (color == -1) {
							/* black edge line */
							color = 0;
							color_set = 0;
						}
					} else {
						/* black edge line */
						color = 0;
						color_set = 0;
					}
				} else if (strcmp(lastid, "colorscheme") == 0) {
					/* scheme to use for colors as int, different names */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge colorscheme";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						csstr = laststring;
					} else if (token == DOT_ID) {
						csstr = lastid;
					} else {
						parser_error_string = "expected schema name at edge colorscheme";
						ect = splay_tree_delete(ect);
						return (1);
					}
					/* parse the name in one of the known schema names */
					cscode = colorschemecode(csstr);
					if (cscode == 0) {
						parser_error_string = "unknown schema name at edge colorscheme";
						ect = splay_tree_delete(ect);
						return (1);
					}
					cscode_set = 1;
				} else if (strcmp(lastid, "comment") == 0) {
					/* comment inserted in output, device dependent */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge comment";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						/* */
					} else {
						parser_error_string = "expected string at edge comment";
						ect = splay_tree_delete(ect);
						return (1);
					}
				} else if (strcmp(lastid, "constraint") == 0) {
					/* at false, edge is not in ranking nodes */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge constraint";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						/* */
					} else if (token == DOT_ID) {
						/* */
					} else {
						parser_error_string = "expected true/false at edge constraint";
						ect = splay_tree_delete(ect);
						return (1);
					}
					/* parse true/false boolean */
				} else if (strcmp(lastid, "decorate") == 0) {
					/* if true, attach edge label in special way */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge decorate";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse true/false boolean */
					if (token == DOT_STRING) {
						/* */
					} else if (token == DOT_ID) {
						/* */
					} else {
						parser_error_string = "expected true/false at edge decorate";
						ect = splay_tree_delete(ect);
						return (1);
					}
				} else if (strcmp(lastid, "dir") == 0) {
					/* direction of edge, forward, back, both or none */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge dir";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						/* */
					} else if (token == DOT_ID) {
						/* */
					} else {
						parser_error_string = "expected direction at edge dir";
						ect = splay_tree_delete(ect);
						return (1);
					}
					/* parse forward, back, both or none */
				} else if (strcmp(lastid, "edgeURL") == 0) {
					/* svg, map only, url escstring */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge edgeURL";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
				} else if (strcmp(lastid, "edgehref") == 0) {
					/* svg, map only, url escstring */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge edgehref";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
				} else if (strcmp(lastid, "edgetarget") == 0) {
					/* svg, map only, url escstring */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge edgetarget";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
				} else if (strcmp(lastid, "edgetooltip") == 0) {
					/* svg, map only, url escstring */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge edgetooltip";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
				} else if (strcmp(lastid, "fontcolor") == 0) {
					/* color for edge label text */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge fontcolor";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						fcolor = colorcode(laststring);
						fcolor_set = 1;
						/* check for unknown color */
						if (fcolor == -1) {
							/* black edge label */
							fcolor = 0;
							fcolor_set = 0;
						}
					} else if (token == DOT_ID) {
						fcolor = colorcode(lastid);
						fcolor_set = 1;
						/* check for unknown color */
						if (fcolor == -1) {
							/* black edge label */
							fcolor = 0;
							fcolor_set = 0;
						}
					} else {
						/* black edge label text */
						fcolor = 0;
						fcolor_set = 0;
					}
				} else if (strcmp(lastid, "fontname") == 0) {
					/* font for edge label text */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge fontname";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						ffontname = laststring;
					} else if (token == DOT_ID) {
						ffontname = lastid;
					} else {
						parser_error_string = "expected name at edge fontname";
						ect = splay_tree_delete(ect);
						return (1);
					}
					/* check for "" */
					if (strlen(ffontname) == 0) {
						ffontname = NULL;
					}
					ffontname_set = 1;
				} else if (strcmp(lastid, "fontsize") == 0) {
					/* font size in points, double, default 14.0 */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge fontsize";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						efontsize = laststring;
					} else if (token == DOT_NUM) {
						efontsize = lastnum;
					} else {
						parser_error_string = "expected size string at edge fontsize";
						ect = splay_tree_delete(ect);
						return (1);
					}
					if (strlen(efontsize) == 0) {
						efontsize = NULL;
					}
					efontsize_set = 1;
				} else if (strcmp(lastid, "headURL") == 0) {
					/* escstring url, svg map only */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge headURL";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
				} else if (strcmp(lastid, "head_lp") == 0) {
					/* edge head label in points */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge head_lp";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						/* */
					} else {
						parser_error_string = "expected point at edge head_lp";
						ect = splay_tree_delete(ect);
						return (1);
					}
				} else if (strcmp(lastid, "headclip") == 0) {
					/* head of edge is clipped, boolean true/false */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge headclip";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse true/false */
				} else if (strcmp(lastid, "headhref") == 0) {
					/* same as headURL */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge headhref";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
				} else if (strcmp(lastid, "headlabel") == 0) {
					/* text label at head of edge, lblstring */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge headlabel";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse lblstring */
				} else if (strcmp(lastid, "headport") == 0) {
					/* head node to attach head of edge, portpos, default center */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge headport";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse portpos */
				} else if (strcmp(lastid, "headtarget") == 0) {
					/* svg map only */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge headtarget";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse escstring */
				} else if (strcmp(lastid, "headtooltip") == 0) {
					/* ntooltip at head of edge */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge headtooltip";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse escstring */
				} else if (strcmp(lastid, "href") == 0) {
					/* same as URL, see above */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge href";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse url string */
				} else if (strcmp(lastid, "id") == 0) {
					/* id for graph objects, to use multiple maps */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge id";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse escstring */
				} else if (strcmp(lastid, "label") == 0) {
					/* edge label, labelstring fixme can be escstring */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge label";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						elabel = laststring;
						elabel_set = 1;
						/* check for "" empty string */
						if (strlen(laststring) == 0) {
							elabel = NULL;
							elabel_set = 1;
						}
					} else if (token == DOT_ID) {
						elabel = lastid;
						elabel_set = 1;
						/* check for "" empty string */
						if (strlen(lastid) == 0) {
							elabel = NULL;
							elabel_set = 1;
						}
					} else if (token == DOT_NUM) {
						elabel = lastnum;
						elabel_set = 1;
						/* check for "" empty string */
						if (strlen(lastnum) == 0) {
							elabel = NULL;
							elabel_set = 1;
						}
					} else if (token == DOT_HTML) {
						elabel = lasthtml;
						elabel_set = 1;
						/* ignore html label for now */
						if (0)
							elabel = (char *)0;
					} else if (token == DOT_CHAR) {
						elabel = lastchar;
						elabel_set = 1;
					} else {
						/* something else or issue error */
						elabel = NULL;
						elabel_set = 0;
						parser_error_string = "expected string at edge label option";
						ect = splay_tree_delete(ect);
						return (1);
					}
				} else if (strcmp(lastid, "labelURL") == 0) {
					/* svg map only, url for edge label escstring */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge labelURL";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
				} else if (strcmp(lastid, "labelangle") == 0) {
					/* angle of label, double, default -25 */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge labelangle";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						/* */
					} else if (token == DOT_NUM) {
						/* */
					} else {
						parser_error_string = "expected number at edge labelangle";
						ect = splay_tree_delete(ect);
						return (1);
					}
				} else if (strcmp(lastid, "labeldistance") == 0) {
					/* scaling factor, double, default 1, */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge labeldistance";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						/* */
					} else if (token == DOT_NUM) {
						/* */
					} else {
						parser_error_string = "expected number at edge labeldistance";
						ect = splay_tree_delete(ect);
						return (1);
					}
				} else if (strcmp(lastid, "labelfloat") == 0) {
					/* if true edge labels less constrained, true/false */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge labelfloat";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse true/false */
				} else if (strcmp(lastid, "labelfontcolor") == 0) {
					/* color for label */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge labelfontcolor";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
				} else if (strcmp(lastid, "labelfontname") == 0) {
					/* font for edge label */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge labelfontname";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
				} else if (strcmp(lastid, "labelfontsize") == 0) {
					/* fontsize for edge headlabel/taillabel, default 14 */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge labelfontsize";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						efontsize = laststring;
					} else if (token == DOT_NUM) {
						efontsize = lastnum;
					} else {
						parser_error_string = "expected size string at edge labelfontsize";
						ect = splay_tree_delete(ect);
						return (1);
					}
					if (strlen(efontsize) == 0) {
						efontsize = NULL;
					}
					efontsize_set = 1;
				} else if (strcmp(lastid, "labelhref") == 0) {
					/* same as labelURL svg map only */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge labelhref";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
				} else if (strcmp(lastid, "labeltarget") == 0) {
					/* svg map only which browser window for url, escstring */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge labeltarget";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
				} else if (strcmp(lastid, "labeltooltip") == 0) {
					/* tooltip, svg map only, */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge labeltooltip";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
				} else if (strcmp(lastid, "layer") == 0) {
					/* layer in which edge is present */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge layer";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse layerlist arg */
				} else if (strcmp(lastid, "len") == 0) {
					/* fdp, neato only, preferred edge length */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge len";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* silently skip edge len=0.24 , is in scons data */
					if (0) {
						parser_error_string = "un-expected edge len is fdp, neato only";
						ect = splay_tree_delete(ect);
						return (1);
					}
				} else if (strcmp(lastid, "lhead") == 0) {
					/* logical head of edge, string */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge lhead";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						/* */
					} else {
						parser_error_string = "expected string at edge lhead";
						ect = splay_tree_delete(ect);
						return (1);
					}
				} else if (strcmp(lastid, "lp") == 0) {
					/* label pos in points */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge lp";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse point arg */
				} else if (strcmp(lastid, "ltail") == 0) {
					/* logical tail, see lhead */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge ltail";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						/* */
					} else {
						parser_error_string = "expected string at edge ltail";
						ect = splay_tree_delete(ect);
						return (1);
					}
				} else if (strcmp(lastid, "minlen") == 0) {
					/* min. rank difference between head and tail, int, default 1 */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge minlen";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						/* */
					} else if (token == DOT_NUM) {
						/* */
					} else {
						parser_error_string = "expected int number at edge minlen";
						ect = splay_tree_delete(ect);
						return (1);
					}
				} else if (strcmp(lastid, "nojustify") == 0) {
					/* justify multi-line labels */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge nojustify";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse true/false */
				} else if (strcmp(lastid, "penwidth") == 0) {
					/* line thickness, double, default 1.0 */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge penwidth";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						penwstr = laststring;
					} else if (token == DOT_NUM) {
						penwstr = lastnum;
					} else {
						parser_error_string = "expected double number at edge penwidth";
						ect = splay_tree_delete(ect);
						return (1);
					}
					if (penwstr == NULL) {
						parser_error_string = "unexpected double number at edge penwidth nil string";
						ect = splay_tree_delete(ect);
						return (1);
					}
					/* check for "" */
					if (strlen(penwstr) == 0) {
						thickness = 0;	/* default */
						thickness_set = 1;
					} else {
						/* can add extra check on chars left */
						thicknessd = strtod(penwstr, NULL);
						if (errno) {
							parser_error_string =
							    "unexpected double number at edge penwidth cannot convert";
							ect = splay_tree_delete(ect);
							return (1);
						}
						thicknessd = round(thicknessd);
						thickness = (int)thicknessd;
						/* limiter, see main.h */
						if (thickness < 0) {
							thickness = 0;
						}
						if (thickness > 15) {
							thickness = 15;
						}
						thickness_set = 1;
					}
				} else if (strcmp(lastid, "pos") == 0) {
					/* spline control points */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge pos";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse points */
				} else if (strcmp(lastid, "samehead") == 0) {
					/* edges with same heads */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge samehead";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse string */
					if (token == DOT_STRING) {
						/* */
					} else {
						parser_error_string = "expected string at edge samehead";
						ect = splay_tree_delete(ect);
						return (1);
					}
				} else if (strcmp(lastid, "sametail") == 0) {
					/* edges with same tails */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge sametail";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					if (token == DOT_STRING) {
						/* */
					} else {
						parser_error_string = "expected sting at edge sametail";
						ect = splay_tree_delete(ect);
						return (1);
					}
				} else if (strcmp(lastid, "showboxes") == 0) {
					/* postscipt dfault 0, int */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge showboxes";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse int */
				} else if (strcmp(lastid, "style") == 0) {
					/* edge style */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge style";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* default settings for edge line */
					style = ESTYLE_NORMAL;
					/* to update, check for DOT_ID and compare with lastid */
					/* fixme there are more edge styles */
					if (token == DOT_STRING) {
						stylestr = laststring;
					} else if (token == DOT_ID) {
						stylestr = lastid;
					} else {
						parser_error_string = "expected string at edge style";
						ect = splay_tree_delete(ect);
						return (1);
					}
					/* check for "" empty string */
					if (strlen(stylestr) == 0) {
						style = ESTYLE_NORMAL;
					} else if (strncmp(stylestr, "bold", 4) == 0) {
						style = ESTYLE_NORMAL;
						thickness = 2;
						thickness_set = 1;
					} else if (strncmp(stylestr, "normal", 6) == 0) {
						style = ESTYLE_NORMAL;
					} else if (strncmp(stylestr, "solid", 5) == 0) {
						style = ESTYLE_SOLID;
					} else if (strncmp(stylestr, "dotted", 6) == 0) {
						style = ESTYLE_DOTTED;
					} else if (strncmp(stylestr, "dashed", 6) == 0) {
						style = ESTYLE_DASHED;
					} else if (strncmp(stylestr, "invis", 5) == 0) {
						style = ESTYLE_INVIS;	/* gcc data has invis edge from start to end  */
					} else {
						/* something else */
						style = ESTYLE_NORMAL;
					}
					/* check if there is extra modify arg */
					stylep = strchr(stylestr, ',');
					if (stylep) {
						if (strncmp(stylep, ",bold", 5) == 0) {
							thickness = 2;
							thickness_set = 1;
						}
					}
					style_set = 1;
				} else if (strcmp(lastid, "tailURL") == 0) {
					/* svg map only */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge tailURL";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse url */
				} else if (strcmp(lastid, "tail_lp") == 0) {
					/* pos of edge tail label */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge tailURL";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse points */
				} else if (strcmp(lastid, "tailclip") == 0) {
					/* if true tail of edge is at node boundary, else at node center */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge tailclip";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse true/false */
				} else if (strcmp(lastid, "tailhref") == 0) {
					/* same as tailURL svg map only */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge tailhref";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse url */
				} else if (strcmp(lastid, "taillabel") == 0) {
					/* label at tail of edge */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge taillabel";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse labelstring */
				} else if (strcmp(lastid, "tailport") == 0) {
					/* where attach tail edge at tail port */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge tailport";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse port pos */
				} else if (strcmp(lastid, "tailtarget") == 0) {
					/* svg map only, which browsr window */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge tailtarget";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse escstring */
				} else if (strcmp(lastid, "tailtooltip") == 0) {
					/* svg map only, tooltip at edge tail */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge tailtooltip";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse escstring */
				} else if (strcmp(lastid, "target") == 0) {
					/* svg map only, which browser window */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge target";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse escstring */
				} else if (strcmp(lastid, "tooltip") == 0) {
					/* svg map only tooltip for edge */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge tooltip";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse escstring */
				} else if (strcmp(lastid, "weight") == 0) {
					/* weight of edge, the higer the more shorter, vertical the edge is */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge weight";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse int, default 0 */
					if (token == DOT_STRING) {
						/* */
					} else if (token == DOT_NUM) {
						/* */
					} else {
						parser_error_string = "expected number at edge weight";
						ect = splay_tree_delete(ect);
						return (1);
					}
					/* this must be int, not double */
				} else if (strcmp(lastid, "xlabel") == 0) {
					/* external label */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge xlabel";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse labelstring */
				} else if (strcmp(lastid, "xlp") == 0) {
					/* pos of xlabel in points */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge xlp";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* parse points list */
				} else {
					/* something else followed by '=' and a arg */
					token = dot_lex();
					if (token != DOT_IS) {
						parser_error_string = "expected = at edge unknown-option";
						ect = splay_tree_delete(ect);
						return (1);
					}
					token = dot_lex();
					/* skip silently */
				}
			} else {
				/* not id, can be char, number, html, string */
				token = dot_lex();
				if (token != DOT_IS) {
					parser_error_string = "expected = at edge unknown-option";
					ect = splay_tree_delete(ect);
					return (1);
				}
				token = dot_lex();
				/* skip silently */
			}

		}		/* end of for() */
	} else {
		/* not a [ so unlex */
		dot_unlex();
	}

	/* regular edge or self edge to add */

	/* scan the cached edges */
	for (i = 0; i < nect; i++) {
		/* get splay data */
		spn = splay_tree_lookup(ect, (splay_tree_key) i);
		if (spn == NULL) {
			ect = splay_tree_delete(ect);
			parser_error_string = "unexpected spn at edge";
			return (1);
		}
		ecptr = (struct ecdot *)spn->value;
		if (ecptr == NULL) {
			ect = splay_tree_delete(ect);
			parser_error_string = "unexpected ecptr at edge";
			return (1);
		}
		/* set the from/to name names */
		fname = (char *)ecptr->fromname;
		tname = (char *)ecptr->toname;
		/* create the edge and check the nodes of the edge */
		fnode = uniqnode(fname);
		if (fnode == (struct unode *)0) {
			fnode = unode_new(fname);
			/* set defaults from "node[...]" */
			set_node_defaults(fnode, subg);
			fnode->name = fname;
			/* initial label is node name */
			if (fnode->labeltype) {
				/* use preset */
				if (fnode->label == NULL) {
					fnode->label = fname;
				}
			} else {
				fnode->label = fname;
			}

			fnode->rootedon = subg;
			/* node is defined by edge statement */
			fnode->bitflags.defbyedge = 1;
			fnode->bitflags.defbynode = 0;
			uniqnode_add(fnode->name, fnode);
			parsednl_add(fnode);
		} else {
			/* already defined node */
		}
		tnode = uniqnode(tname);
		if (tnode == (struct unode *)0) {
			tnode = unode_new(tname);
			/* set defaults from "node[...]" */
			set_node_defaults(tnode, subg);
			tnode->name = tname;
			/* initial label is node name */
			if (tnode->labeltype) {
				/* use preset */
				if (tnode->label == NULL) {
					tnode->label = tname;
				}
			} else {
				tnode->label = tname;
			}

			tnode->rootedon = subg;
			/* node is defined by edge statement */
			tnode->bitflags.defbyedge = 1;
			tnode->bitflags.defbynode = 0;
			uniqnode_add(tnode->name, tnode);
			parsednl_add(tnode);
		} else {
			/* already defined node */
		}

		/* create new edge */
		ue = uedge_new(fnode, tnode);

		/* set edge defaults first */
		set_edge_defaults(ue, subg);

		/* now set edge options */

		/* black or colored edge line */
		if (color_set) {
			ue->color = color;
		}
		/* color of edge label text */
		if (fcolor_set) {
			ue->textcolor = fcolor;
		}
		/* font for edge label */
		if (ffontname_set) {
			ue->fontname = ffontname;
		}
		/* label with posible esc chars */
		if (elabel_set) {
			/* todoe un-esc string with control chars */
			ue->label = elabel;
		}
		/* edge line thickness 0..15 */
		if (thickness_set) {
			ue->bitflags.thickness = thickness;
		}
		/* solid or dotted or other edge line */
		if (style_set) {
			ue->bitflags.style = style;
		}
		/* default fontsize */
		if (efontsize_set) {
			ue->fontsize = efontsize;
		}
		/* color scheme */
		if (cscode_set) {
			ue->bitflags.cscheme = cscode;
		}
		ue->rootedon = subg;

		/* add edge to edgelist */
		parsedel_add(ue);
	}

	ect = splay_tree_delete(ect);

	/* end of edge */
	return (1);
}

/* has token a compass point return it' s string or (char *) 0 if not */
static char *is_a_dotne_iscompass(void)
{
	char *ret = NULL;
	/*
	 * compass point is one of:
	 *  "n","ne","e","se","s","sw","w","nw","c","_"
	 */
	if (token == DOT_STRING) {
		if (strcmp(laststring, "n") == 0) {
			ret = (char *)"n";
		} else if (strcmp(laststring, "ne") == 0) {
			ret = (char *)"ne";
		} else if (strcmp(laststring, "e") == 0) {
			ret = (char *)"e";
		} else if (strcmp(laststring, "se") == 0) {
			ret = (char *)"se";
		} else if (strcmp(laststring, "s") == 0) {
			ret = (char *)"s";
		} else if (strcmp(laststring, "sw") == 0) {
			ret = (char *)"sw";
		} else if (strcmp(laststring, "w") == 0) {
			ret = (char *)"w";
		} else if (strcmp(laststring, "nw") == 0) {
			ret = (char *)"nw";
		} else if (strcmp(laststring, "c") == 0) {
			ret = (char *)"c";
		} else if (strcmp(laststring, "_") == 0) {
			ret = (char *)"_";
		} else {
			ret = (char *)0;
		}
	} else if (token == DOT_ID) {
		if (strcmp(lastid, "ne") == 0) {
			ret = (char *)"ne";
		} else if (strcmp(lastid, "sw") == 0) {
			ret = (char *)"sw";
		} else if (strcmp(lastid, "se") == 0) {
			ret = (char *)"se";
		} else if (strcmp(lastid, "nw") == 0) {
			ret = (char *)"nw";
		} else {
			ret = (char *)0;
		}
	} else if (token == DOT_CHAR) {
		if (lastchar[0] == 'n') {
			ret = (char *)"n";
		} else if (lastchar[0] == 'e') {
			ret = (char *)"e";
		} else if (lastchar[0] == 's') {
			ret = (char *)"s";
		} else if (lastchar[0] == 'w') {
			ret = (char *)"w";
		} else if (lastchar[0] == 'c') {
			ret = (char *)"c";
		} else if (lastchar[0] == '_') {
			ret = (char *)"_";
		} else {
			ret = (char *)0;
		}
	} else {
		ret = (char *)0;
	}

	return ((char *)ret);
}

/* end */
